package lowijs.XfireClient;

import java.net.*;
import java.io.*;

import lowijs.XfireClient.event.*;
import lowijs.XfireClient.packet.*;

public class XfireClient extends Thread implements XfireObserver {
	private DataInputStream in = null;
	private DataOutputStream out = null;
	private byte[] buffer;
	private String username, password, nickname, statustext = "Online";
	private boolean runThread = true;
	
	public XfireClient() {
		EventManager.addObserver(this);
	}
	
	/**
	 * Connect to Xfire using the username and password provided in the constructor.
	 */
	public void connect(String username, String password) {
		this.username = username;
		this.password = password;
		try {
			if (!runThread)
				return;
			Socket s = new Socket("cs.xfire.com", 25999);
			in = new DataInputStream(s.getInputStream());
			out = new DataOutputStream(s.getOutputStream());
			login();
		} catch (IOException ioe) {
			disconnect();
		}
	}
	
	public void disconnect() {
		EventManager.removeObserver(this);
		runThread = false;
		
		try {
			out.write(new byte[] { 0, 0, 0, 0 }); // sabotage the stream			
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} finally {
			try {
				out.close();
				in.close();
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
			FriendManager.getInstance().cleanup();
			EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_OFFLINE));
		}
	}
	
	public void run() {
		setName("Xfire Reader Thread");
		
		while(runThread) {
			readBytes();
			debug(buffer);
			
			switch(buffer[0] & 0xFF) {
			case 0x80: // salt
				SaltPacket sp = new SaltPacket(buffer);
				LoginPacket lp =
					new LoginPacket(username, password, sp.getSalt());
				write(lp.getBytes());
				break;
			case 0x81: // auth failed
				disconnect();
				EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_LOGINFAIL));
				break;
			case 0x82: // loginreply
				LoginReplyPacket lrp = new LoginReplyPacket(buffer);
				nickname = lrp.getNickname();
				break;
			case 0x83: // friendslist
				new FriendslistPacket(buffer);
				break;
			case 0x84: // friend online
				new FriendStatusPacket(buffer);
				break;
			case 0x85: // receive message
				ReceiveMessagePacket rmp = new ReceiveMessagePacket(buffer);
				if (rmp.getMessageType() == ReceiveMessagePacket.MSGTYPE_IM) {
					AckImPacket amp =
						new AckImPacket(rmp.getSid(), rmp.getImIndex());
					write(amp.getBytes());
				}
				break;
			case 0x87: // friend in game
				new FriendGamePacket(buffer);
				break;
			case 0x91: // disconnected with reason
				disconnect();
				EventManager.fireEvent(new DatalessEvent(XfireEvent.XF_DCREASON));
				break;
			case 0x9a: // friend status text
				new FriendStatusTextPacket(buffer);
				break;
			case 0xac:
				new ScreenshotsPacket(buffer);
				break;
			}
		}
	}
	
	private void login() {
		// initialize connection with the 'UA01' packet
		write("UA01".getBytes());
		
		// send the version packet
		VersionPacket vp = new VersionPacket();
		write(vp.getBytes());
		
		// start the reader thread
		start();
	}

	/**
	 * Returns the nickname set for this Xfire user.
	 * @return the current nickname.
	 */
	public String getNickname() {
		return nickname;
	}
	
	/**
	 * Sets a new nickname for this Xfire user.
	 * @param nick the nickname to set.
	 */
	public void setNickname(String nickname) {
		ChangeNickPacket cnp = new ChangeNickPacket(nickname);
		write(cnp.getBytes());
		this.nickname = nickname;
	}
	
	public String getStatusText() {
		return statustext;
	}
	
	public void setStatusText(String statustext) {
		ChangeStatusTextPacket cstp = new ChangeStatusTextPacket(statustext);
		write(cstp.getBytes());
		this.statustext = statustext;
	}
	
	public String getUsername() {
		return username;
	}
	
	public void handleEvent(XfireEvent event) {
		switch(event.getEventType()) {
		case XfireEvent.XF_SENDIM:
			SendImEvent e = (SendImEvent)event;
			SendImPacket sip = new SendImPacket(e.getSid(), e.getImindex(),
					e.getMessage());
			write(sip.getBytes());
			return;
		case XfireEvent.XF_REQINFO:
			Friend f = ((RequestInfoEvent)event).getFriend();
			RequestInfoPacket rip = new RequestInfoPacket(f.getUserid());
			write(rip.getBytes());
			return;
		}
	}
	
	/*
	 * I/O and debugging methods
	 */
	private void readBytes() {
		try {
			byte[] numBytes = new byte[2];
			in.read(numBytes, 0, 2);
			int low = numBytes[0] & 0xFF, high = numBytes[1] & 0xFF;
			int len = (0x00 | low | (high << 8)) - 2;
			
			if (len <= 0) {
				buffer = new byte[] { 0 };
				return;
			}
			
			buffer = new byte[len];
			in.read(buffer, 0, len);
		} catch (IOException ioe) {
			ioe.printStackTrace();
			disconnect();
		}
	}
	
	public void write(byte[] bs) {
		try {
			out.write(bs);
		} catch (IOException ioe) {
			ioe.printStackTrace();
			disconnect();
		}
	}
	
	private static void debug(byte[] bs) {
		for (byte b : bs) {
			System.out.print(String.format("%02x", b) + " ");
		}
		System.out.println();
	}
}
